#include <stdio.h>
#include <assert.h>

#include <ppapi/cpp/size.h>
#include <ppapi/cpp/rect.h>

#include <ppapi/cpp/module.h>
#include <ppapi/cpp/instance.h>
#include <ppapi/cpp/completion_callback.h>
#include <ppapi/cpp/graphics_3d.h>
#include <ppapi/cpp/graphics_3d_client.h>
#include <ppapi/cpp/dev/surface_3d_dev.h>
#ifdef NACL
#include <ppapi/gles2/gl2ext_ppapi.h>
#else
#include <ppapi/lib/gl/gles2/gl2ext_ppapi.h>
#endif
#include <ppapi/c/ppb_opengles2.h>
#include <ppapi/cpp/input_event.h>
#include <ppapi/c/ppb_input_event.h>
#include <ppapi/cpp/fullscreen.h>
#include <ppapi/cpp/mouse_lock.h>

#include "test.h"

// PPAPI specifics in this file.
// Test.cpp is "pupe" opengl es

class Test2Instance : public pp::Instance, public pp::Graphics3DClient, public pp::MouseLock
{
public:
  Test2Instance(PP_Instance instance, pp::Module* module) :
    pp::Instance(instance),
    pp::Graphics3DClient(this),
    pp::MouseLock(this),
    m_completionFactory(this),
    m_fullscreen(this)
  {
    m_gl = NULL;
    m_lastAbsMousePos.set_x(-1);
    m_lastAbsMousePos.set_y(-1);
  }

  virtual ~Test2Instance()
  {
  }

private:
  virtual bool Init(uint32_t argc, const char* argn[], const char* argv[])
  {
    RequestInputEvents(PP_INPUTEVENT_CLASS_MOUSE);
    RequestFilteringInputEvents(PP_INPUTEVENT_CLASS_KEYBOARD);

    m_gl = static_cast<const struct PPB_OpenGLES2*>(pp::Module::Get()->GetBrowserInterface(PPB_OPENGLES2_INTERFACE));
    assert(m_gl);

    bool res = glInitializePPAPI(pp::Module::Get()->get_browser_interface());
    assert(res);

    return true;
  }

  virtual void DidChangeView(const pp::Rect &position, const pp::Rect &clip)
  {
    printf("DidChangeView: %u %u\n", position.size().width(), position.size().height());

    if(m_gfx3d.is_null())
    {
      int32_t attribs[] =
      {
        //PP_GRAPHICS3DATTRIB_ALPHA_SIZE, 8,
        PP_GRAPHICS3DATTRIB_DEPTH_SIZE, 24,
        //PP_GRAPHICS3DATTRIB_STENCIL_SIZE, 8,
        //PP_GRAPHICS3DATTRIB_SAMPLES, 0,
        //PP_GRAPHICS3DATTRIB_SAMPLE_BUFFERS, 0,
        PP_GRAPHICS3DATTRIB_WIDTH, position.size().width(),
        PP_GRAPHICS3DATTRIB_HEIGHT, position.size().height(),
        PP_GRAPHICS3DATTRIB_NONE
      };

      m_gfx3d = pp::Graphics3D(this, attribs);
      assert(!m_gfx3d.is_null());
      BindGraphics(m_gfx3d);
      glSetCurrentContextPPAPI(m_gfx3d.pp_resource());

      m_test.init();
      m_test.resize(position.size().width(), position.size().height());
      m_size = position.size();

      Render();
    }

    if(position.size() != m_size)
    {
      glSetCurrentContextPPAPI(0);
      // note: chrome 98468 crashes in the call to ResizeBuffers()
      m_gfx3d.ResizeBuffers(position.size().width(), position.size().height());
      glSetCurrentContextPPAPI(m_gfx3d.pp_resource());
      m_test.resize(position.size().width(), position.size().height());
    }
  }

  virtual void DidChangeFocus(bool has_focus)
  {
    printf("DidChangeFocus: %d\n", has_focus);
  }

  virtual bool HandleInputEvent(const pp::InputEvent& event)
  {
    if(event.GetType() == PP_INPUTEVENT_TYPE_MOUSEDOWN)
    {
      pp::MouseInputEvent mouseEvent(event);
      if(mouseEvent.GetButton() == PP_INPUTEVENT_MOUSEBUTTON_LEFT)
      {
        printf("mouse down");
        LockMouse(m_completionFactory.NewCallback(&Test2Instance::LockMouseDone));

        return true;
      }
    }
    else if(event.GetType() == PP_INPUTEVENT_TYPE_MOUSEMOVE)
    {
      pp::MouseInputEvent mouseEvent(event);
      pp::Point absPos = mouseEvent.GetPosition();
      pp::Point relPos = mouseEvent.GetMovement();

      int deltaX = absPos.x()-m_lastAbsMousePos.x() + relPos.x();
      int deltaY = absPos.y()-m_lastAbsMousePos.y() + relPos.y();
      m_lastAbsMousePos = absPos;

      printf("abs:%d,%d  rel:%d,%d  -> %d,%d\n", absPos.x(), absPos.y(), relPos.x(), relPos.y(), deltaX, deltaY);
      m_test.mouseMoved(deltaX, deltaY);

      return true;
    }
    else if(event.GetType() == PP_INPUTEVENT_TYPE_KEYDOWN)
    {
      pp::KeyboardInputEvent keyDownEvent(event);
      if(keyDownEvent.GetKeyCode() == 'F')
      {
        printf("%s fullscreen\n", m_fullscreen.IsFullscreen() ? "leaving" : "entering");
        if(!m_fullscreen.SetFullscreen(!m_fullscreen.IsFullscreen()))
        {
          printf("failed to change fullscreen mode\n");
        }
        return true;
      }
    }
    return false;
  }

  virtual void Graphics3DContextLost()
  {
    assert(0);
  }

  virtual void MouseLockLost()
  {
    printf("lost mouse lock!\n");
  }

  void Render()
  {
    m_test.render();

    m_gfx3d.SwapBuffers(m_completionFactory.NewCallback(&Test2Instance::RenderDone, 0));
  }

  void RenderDone(int32_t result, int picture_buffer_id)
  {
    Render();
  }

  void LockMouseDone(int32_t result)
  {
    printf("LockMouseDone result: %d\n", result);
  }

  pp::CompletionCallbackFactory<Test2Instance> m_completionFactory;
  const PPB_OpenGLES2 *m_gl;
  pp::Size m_size;
  pp::Graphics3D m_gfx3d;
  pp::Fullscreen m_fullscreen;
  pp::Point m_lastAbsMousePos;
  Test m_test;
};

///////////////////////////////////////////////////////////////////////////////

class Test2Module : public pp::Module
{
 public:
  Test2Module() : pp::Module() {}
  virtual ~Test2Module() {}
  virtual pp::Instance *CreateInstance(PP_Instance instance) { return new Test2Instance(instance, this); }
};

namespace pp
{
Module *CreateModule()
{
  fprintf(stderr, "CreateModule\n");
  fflush(stderr);
  return new Test2Module();
}
}
